package controller

import (
	"alan-snippet/internal/dto"
	"alan-snippet/internal/models"
	"alan-snippet/internal/response"
	"alan-snippet/internal/service"
	"alan-snippet/pkg/erru"
	"log"
	"strconv"

	"github.com/gin-gonic/gin"
)

func CreateSnippet(c *gin.Context) {
	// resolve parms
	var createParm dto.CreateSnippetRequestDTO
	err := c.ShouldBindJSON(&createParm)
	// TODO: err handle
	if err != nil {
		response.FailWithCode(c, response.ErrCodeParmResolve)
		return
	}
	// service handle request
	userId := c.MustGet("userID").(uint)
	snippet, err := service.CreateSnippet(createParm, userId)
	if err != nil {
		response.FailWithCode(c, response.ErrCodeInternal)
		return
	}

	// encapsuate DTO
	res := modelToSnippetResponseDTO(snippet)
	response.OkWithData(c, res)
}

// little hard
func SnippetList(c *gin.Context) {

	// 1.resolve query parms
	var listParm dto.ListSnippetsRequestDTO
	err := c.ShouldBindQuery(&listParm)
	if err != nil {
		response.FailWithCode(c, response.ErrCodeParmResolve)
		return
	}
	if listParm.Page == 0 {
		listParm.Page = 1
	}
	if listParm.Size == 0 {
		listParm.Size = 10
	}
	log.Println(listParm)

	// 2.call service
	snippets, total, err := service.ListSnippet(listParm)
	if err != nil {
		response.FailWithCode(c, response.ErrCodeInternal)
		return
	}

	// 3.handle data
	snippetsRes := make([]dto.SnippetResponseDTO, 0, len(snippets))
	for _, snippet := range snippets {
		snippetDTO := modelToSnippetResponseDTO(&snippet)
		snippetDTO.Content = "" // 清空内容，减少传输数据量
		snippetsRes = append(snippetsRes, snippetDTO)
	}
	listDto := dto.ListSnippetsResponseDTO{
		Snippets: snippetsRes,
		Total:    total,
	}
	response.OkWithData(c, listDto)
}

func GetSnippetById(c *gin.Context) {
	// only one parm donnot need a dto
	id := c.Param("id")
	idNum, err := strconv.Atoi(id)
	if err != nil {
		response.FailWithCode(c, response.ErrCodeParmResolve)
		return
	}
	snippet, err := service.GetSnippetById(idNum)
	if err != nil {
		response.FailWithCode(c, response.ErrCodeInternal)
		return
	}
	response.OkWithData(c, modelToSnippetResponseDTO(snippet))
}

func GetPopularSnippets(c *gin.Context) {
	snippets, err := service.GetPopularSnippets()
	if err != nil {
		response.FailWithCode(c, response.ErrCodeInternal)
		return
	}
	res := make([]dto.SnippetResponseDTO, 0)
	for _, s := range snippets {
		res = append(res, modelToSnippetResponseDTO(s))
	}
	response.OkWithData(c, res)
}

func UpdateSnippet(c *gin.Context) {
	snippetId := c.Param("id")
	_, err := strconv.Atoi(snippetId)
	if err != nil {
		// response.FailWithCode(c, response.ErrCodeParmResolve)
		// 对于参数绑定错误，我们可以使用 c.Error 抛出参数无效错误
		_ = c.Error(erru.ErrInvalidParams.Wrap(err))
		return
	}

	var updateDTO dto.UpdateSnippetRequestDTO
	err = c.ShouldBindJSON(&updateDTO)
	if err != nil {
		// response.FailWithCode(c, response.ErrCodeParmResolve)
		_ = c.Error(erru.ErrInvalidParams.Wrap(err))
		return
	}
	// log.Println(snippetIdNum)
	// log.Println(updateDTO)
	userId := c.MustGet("userID").(uint)
	snippet, err := service.UpdateSnippet(snippetId, &updateDTO, userId)
	if err != nil {
		// response.Fail(c, response.ErrCodeInternal, nil, err.Error())
		// 关键！无论 service 返回什么错误，都直接交给 c.Error
		// service过来的err都是封装过的
		_ = c.Error(err)
		return
	}
	resDTO := modelToSnippetResponseDTO(snippet)
	response.OkWithData(c, resDTO)
}

func DeleteSnippet(c *gin.Context) {
	snippetIdStr := c.Param("id")
	snippetId, _ := strconv.Atoi(snippetIdStr)
	currentUserId := c.MustGet("userID").(uint)

	err := service.DeleteSnippet(snippetId, currentUserId)
	if err != nil {
		response.FailWithMsg(c, err.Error())
		return
	}

	response.OkWithMsg(c, "删除成功")
}

// modelToSnippetResponseDTO 是一个辅助函数，用于将内部的 model 转换为外部的 DTO
// 这是一种常见的实践，可以避免直接暴露数据库模型结构
func modelToSnippetResponseDTO(snippet *models.Snippet) dto.SnippetResponseDTO {
	tagInfos := make([]dto.TagResponseDTO, 0, len(snippet.Tags))
	for _, t := range snippet.Tags {
		tagInfos = append(tagInfos, dto.TagResponseDTO{
			ID:   t.ID,
			Name: t.Name,
		})
	}
	return dto.SnippetResponseDTO{
		ID:      snippet.ID,
		Title:   snippet.Title,
		Content: snippet.Content,
		Tags:    tagInfos,
	}
}
